 
  

 






<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
<html>

<!-- Mirrored from www.javapractices.com/topic/TopicAction.do?Id=162 by HTTrack Website Copier/3.x [XR&CO'2010], Sun, 12 Jun 2011 17:28:07 GMT -->
<!-- Added by HTTrack --><meta http-equiv="content-type" content="text/html;charset=UTF-8"><!-- /Added by HTTrack -->
<head>
 <title>
  Java Practices -> Sort table rows
 </title>
 <link rel="stylesheet" type="text/css" href="../stylesheet8.css" media="all">
 
 <link rel="shortcut icon" href='../images/favicon.ico' type="image/vnd.microsoft.icon">
 <meta name="description" content="Concise presentations of java programming practices, tasks, and conventions, amply illustrated with syntax highlighted code examples.">
 
 <meta name='keywords' content='Comparator,Quote,QuoteField,QuoteSorterFactory,QuoteTable,TableSortIndicator,sort,swing,table,java,java programming,java practices,java idiom,java style,java design patterns,java coding conventions,'>
 
 
</head>
 
<body>


<div class='menu-bar'>
 
  <a href='../home/HomeAction.html' title='Table of Contents'>Home</a> |
  <a href='../vote/VoteSummaryAction-2.html' title='View Poll Results'>Poll</a> |
   
  <A href='../feedback/FeedbackAction451f-2.html?Operation=Show' title='Send Your Feedback'>Wiki</a> |
  <b><a href='../source/SourceAction-2.html' title='Grab Source Code'>Source Code</a></b><IMG class='no-margin' SRC="../images/goldstar.gif" ALT=""> |

  <a href='http://www.web4j.com/Java_Web_Application_Framework_Overview.jsp?From=1' title='Free Download - Java Web Application Framework'><b>WEB4J</b></a> |
  
  <a href='http://www.date4j.net/' title='Replacement for java.util.Date'><b>DATE4J</b></a> |

   <a href='../references/ReferencesAction-2.html' title='References'>Links</a>
   
  <form action='http://www.javapractices.com/search/SearchAction.do' method='get' class='search-form'>
   <input type='text' name='SearchTerms' value="" size=12 maxlength=50 class='search'>
   <input type='submit' value="Search">
  </form>
 
</div>

<P>



  

 






<p class="display-messages">

 

 

</p>


<div class="main-layout">
 
   

 




<div class='page-title'>Sort table rows</div>

<div class='main-body'>
 
<br>Sorting tables is a common task in Swing applications. JSE 6 has added a sorting 
and filtering mechanism to 
<tt><a href="http://java.sun.com/javase/6/docs/api/javax/swing/JTable.html">JTable</a></tt>, using 
<tt><a href="http://java.sun.com/javase/6/docs/api/javax/swing/RowSorter.html">RowSorter</a></tt>.

<p>The following example uses JSE 1.4. It sorts a particular table using any column,
and in any direction (ascending order or descending order). Much of this
example can be applied to any table, with the exception of the <tt>Comparator</tt>
implementations, which are specific to the underlying data.

<P>The example uses a small 
<a href='http://java.sun.com/javase/6/docs/api/javax/swing/JTable.html'>JTable</a>
having data related to movies - title, date viewed, and so on.

<P>The implementation is split between 3 classes :
<ul>
 <li>Movie - a Model Object which does most of the work by defining several Comparator classes (which do the actual sorting) 
 <li>MovieTableModel - a custom <a href='http://java.sun.com/javase/6/docs/api/javax/swing/table/TableModel.html'>TableModel</a> which wraps a Model Object, and decides which <a href='http://java.sun.com/javase/6/docs/api/java/util/Comparator.html'>Comparator</a> to use
 <li>MainWindow - contains the JTable, and uses a <a href='http://java.sun.com/javase/6/docs/api/java/awt/event/MouseAdapter.html'>MouseAdapter</a> to listen for clicks on the table's column header
</ul>



<P>Here is the Movie class.
<br>
<PRE>

<span class='keyword'>package</span> hirondelle.movies.edit;

<span class='keyword'>import</span> java.math.BigDecimal;
<span class='keyword'>import</span> java.util.*;

<span class='keyword'>import</span> hirondelle.movies.exception.InvalidInputException;
<span class='keyword'>import</span> hirondelle.movies.util.Util;

<span class='comment'>/**
 Data-centric class encapsulating all fields related to movies. 
 
 &lt;P&gt;This class exists  in order to encapsulate, validate, and sort movie information.
  This class is used both to validate user input, and act as a 'transfer object' when 
  interacting with the database.
 
  &lt;P&gt;&lt;b&gt;This class would greatly benefit from a JUnit test class, to test its data 
  validation and sorting.&lt;/b&gt;
*/</span>
<span class='keyword'>final</span> <span class='keyword'>class</span> Movie <span class='keyword'>implements</span> Comparable&lt;Movie&gt;{

  <span class='comment'>/**
   Constructor taking regular Java objects natural to the domain.
   
   &lt;P&gt;When the user has entered text, this constructor is called indirectly, through 
   {@link #Movie(String, String, String, String, String)}.
   
   @param aId optional, the database identifer for the movie. This item is optional since, 
   for 'add' operations,  it has yet to be assigned by the database.
   @param aTitle has content, name of the movie
   @param aDateViewed optional, date the movie was screened by the user
   @param aRating optional, in range 0.0 to 10.0
   @param aComment optional, any comment on the movie
  */</span>
  Movie(
    String aId, String aTitle, Date aDateViewed, BigDecimal aRating, String aComment
  ) <span class='keyword'>throws</span> InvalidInputException {
    fId = aId;
    fTitle = aTitle;
    fDateViewed = aDateViewed;
    fRating = aRating;
    fComment = aComment;
    validateState();
  }
  
  <span class='comment'>/**
   Constructor which takes all parameters as &lt;em&gt;text&lt;/em&gt;.
   
   &lt;P&gt;Raw user input is usually in the form of &lt;em&gt;text&lt;/em&gt;.
   This constructor &lt;em&gt;first&lt;/em&gt; parses such text into the required 'base objects' - 
   {@link Date}, {@link BigDecimal} and so on. If those parse operations &lt;em&gt;fail&lt;/em&gt;, 
   then the user is shown an error message. If N such errors are present in user input, 
   then  N &lt;em&gt;separate&lt;/em&gt; message will be presented for each failure, one by one.
   
   &lt;P&gt;If all such parse operations &lt;em&gt;succeed&lt;/em&gt;, then the "regular" constructor 
   {@link #Movie(String, String, Date, BigDecimal, String)}
   will then be called. It's important to note that this call to the second constructor 
   can in turn result in &lt;em&gt;another&lt;/em&gt; error message being shown to the 
   user (just one this time). 
 */</span>
  Movie(
    String aId, String aTitle, String aDateViewed, String aRating, String aComment
  ) <span class='keyword'>throws</span> InvalidInputException {
      <span class='keyword'>this</span>(
        aId, aTitle, Util.parseDate(aDateViewed, <span class='literal'>"Date Viewed"</span>), 
        Util.parseBigDecimal(aRating, <span class='literal'>"Rating"</span>), aComment
      );
  }
  
  String getId(){ <span class='keyword'>return</span> fId; }
  
  <span class='comment'>/**
   This set method is rather artificial. It results from the toy persistence layer. 
   It's dissatisfying to add this method since the class would otherwise be immutable. 
   Immutability is a highly desirable characteristic.
  */</span>
  <span class='keyword'>void</span> setId(String aId){  fId = aId; }
  
  String getTitle(){ <span class='keyword'>return</span> fTitle; }
  Date getDateViewed(){ <span class='keyword'>return</span> fDateViewed; }
  BigDecimal getRating(){ <span class='keyword'>return</span> fRating; }
  String getComment(){ <span class='keyword'>return</span> fComment; }
  
  <span class='keyword'>@Override</span> <span class='keyword'>public</span> <span class='keyword'>boolean</span> equals(Object aThat){
    <span class='keyword'>if</span> ( <span class='keyword'>this</span> == aThat ) <span class='keyword'>return</span> <span class='keyword'>true</span>;
    <span class='keyword'>if</span> ( !(aThat <span class='keyword'>instanceof</span> Movie) ) <span class='keyword'>return</span> <span class='keyword'>false</span>;
    Movie that = (Movie)aThat;
    <span class='keyword'>return</span> 
      areEqual(<span class='keyword'>this</span>.fTitle, that.fTitle) &amp;&amp; 
      areEqual(<span class='keyword'>this</span>.fDateViewed, that.fDateViewed) &amp;&amp; 
      areEqual(<span class='keyword'>this</span>.fRating, that.fRating) &amp;&amp; 
      areEqual(<span class='keyword'>this</span>.fComment, that.fComment)
    ; 
  }
  
  <span class='keyword'>@Override</span> <span class='keyword'>public</span> <span class='keyword'>int</span> hashCode(){
    <span class='keyword'>int</span> result = <span class='literal'>17</span>;
    result = addHash(result, fTitle);
    result = addHash(result, fDateViewed);
    result = addHash(result, fRating);
    result = addHash(result, fComment);
    <span class='keyword'>return</span> result;
  }
  
  <span class='keyword'>@Override</span> <span class='keyword'>public</span> String toString(){
    <span class='keyword'>return</span> 
      <span class='literal'>"Movie  Id:"</span> + fId + <span class='literal'>" Title:"</span> + fTitle + <span class='literal'>" Date Viewed:"</span> + fDateViewed + 
      <span class='literal'>" Rating:"</span> + fRating + <span class='literal'>" Comment: "</span> + fComment
    ; 
  }
  
  <span class='comment'>/** 
   Default sort by Date Viewed, then Title. 
   Dates have the most recent items listed first. 
 */</span>
  <span class='keyword'>public</span> <span class='keyword'>int</span> compareTo(Movie aThat) {
    <span class='keyword'>if</span> ( <span class='keyword'>this</span> == aThat ) <span class='keyword'>return</span> EQUAL;
   
    <span class='keyword'>int</span> comparison = DESCENDING*comparePossiblyNull(<span class='keyword'>this</span>.fDateViewed, aThat.fDateViewed);
    <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
    
    comparison = <span class='keyword'>this</span>.fTitle.compareTo(aThat.fTitle);
    <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
    
    comparison = comparePossiblyNull(<span class='keyword'>this</span>.fRating, aThat.fRating);
    <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
   
    comparison = comparePossiblyNull(<span class='keyword'>this</span>.fComment, aThat.fComment);
    <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
    
    <span class='keyword'>return</span> EQUAL;
  }
  
  <span class='comment'>/** Sort by Title. */</span>
  <span class='keyword'>static</span> Comparator&lt;Movie&gt; TITLE_SORT = <span class='keyword'>new</span> Comparator&lt;Movie&gt;(){
    <span class='keyword'>public</span> <span class='keyword'>int</span> compare(Movie aThis, Movie aThat) {
      <span class='keyword'>if</span> ( aThis == aThat ) <span class='keyword'>return</span> EQUAL;

      <span class='keyword'>int</span> comparison = aThis.fTitle.compareTo(aThat.fTitle);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      comparison = DESCENDING*comparePossiblyNull(aThis.fDateViewed, aThat.fDateViewed);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      comparison = comparePossiblyNull(aThis.fRating, aThat.fRating);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
     
      comparison = comparePossiblyNull(aThis.fComment, aThat.fComment);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      <span class='keyword'>return</span> EQUAL;
    };
  };
  
  <span class='comment'>/** Sort by Rating (descending), then Date Viewed (descending). */</span>
  <span class='keyword'>static</span> Comparator&lt;Movie&gt; RATING_SORT = <span class='keyword'>new</span> Comparator&lt;Movie&gt;(){
    <span class='keyword'>public</span> <span class='keyword'>int</span> compare(Movie aThis, Movie aThat) {
      <span class='keyword'>if</span> ( aThis == aThat ) <span class='keyword'>return</span> EQUAL;

      <span class='keyword'>int</span> comparison = DESCENDING*comparePossiblyNull(aThis.fRating, aThat.fRating);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;

      comparison = DESCENDING*comparePossiblyNull(aThis.fDateViewed, aThat.fDateViewed);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      comparison = aThis.fTitle.compareTo(aThat.fTitle);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      comparison = comparePossiblyNull(aThis.fComment, aThat.fComment);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      <span class='keyword'>return</span> EQUAL;
    };
  };
  
  <span class='comment'>/** Sort by Comment. */</span>
  <span class='keyword'>static</span> Comparator&lt;Movie&gt; COMMENT_SORT = <span class='keyword'>new</span> Comparator&lt;Movie&gt;(){
    <span class='keyword'>public</span> <span class='keyword'>int</span> compare(Movie aThis, Movie aThat) {
      <span class='keyword'>if</span> ( aThis == aThat ) <span class='keyword'>return</span> EQUAL;

      <span class='keyword'>int</span> comparison = comparePossiblyNull(aThis.fComment, aThat.fComment);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      comparison = aThis.fTitle.compareTo(aThat.fTitle);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      comparison = comparePossiblyNull(aThis.fRating, aThat.fRating);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;

      comparison = DESCENDING*comparePossiblyNull(aThis.fDateViewed, aThat.fDateViewed);
      <span class='keyword'>if</span> ( comparison != EQUAL ) <span class='keyword'>return</span> comparison;
      
      <span class='keyword'>return</span> EQUAL;
    };
  };
  
  <span class='comment'>// PRIVATE //
</span>  <span class='keyword'>private</span> String fId;
  <span class='keyword'>private</span> <span class='keyword'>final</span> String fTitle;
  <span class='keyword'>private</span> <span class='keyword'>final</span> Date fDateViewed;
  <span class='keyword'>private</span> <span class='keyword'>final</span> BigDecimal fRating;
  <span class='keyword'>private</span> <span class='keyword'>final</span> String fComment;
  <span class='keyword'>private</span> <span class='keyword'>static</span> <span class='keyword'>final</span> BigDecimal TEN = <span class='keyword'>new</span> BigDecimal(<span class='literal'>"10.0"</span>);
  <span class='keyword'>private</span> <span class='keyword'>static</span> <span class='keyword'>final</span> <span class='keyword'>int</span> EQUAL = <span class='literal'>0</span>;
  <span class='keyword'>private</span> <span class='keyword'>static</span> <span class='keyword'>final</span> <span class='keyword'>int</span> DESCENDING = -<span class='literal'>1</span>;
  
  <span class='keyword'>private</span> <span class='keyword'>void</span> validateState() <span class='keyword'>throws</span> InvalidInputException {
    InvalidInputException ex = <span class='keyword'>new</span> InvalidInputException();
    
    <span class='keyword'>if</span>( ! Util.textHasContent(fTitle) ) {
      ex.add(<span class='literal'>"Title must have content"</span>);
    }
    <span class='keyword'>if</span> ( fRating != <span class='keyword'>null</span> ){
      <span class='keyword'>if</span> ( fRating.compareTo(BigDecimal.ZERO) &lt; <span class='literal'>0</span> ) {
        ex.add(<span class='literal'>"Rating cannot be less than 0."</span>);
      }
      <span class='keyword'>if</span> ( fRating.compareTo(TEN) &gt; <span class='literal'>0</span> ) {
        ex.add(<span class='literal'>"Rating cannot be greater than 10."</span>);
      }
    }
    <span class='keyword'>if</span> ( ex.hasErrors() ) {
      <span class='keyword'>throw</span> ex;
    }
  }
  
  <span class='keyword'>private</span> <span class='keyword'>boolean</span> areEqual(Object aThis, Object aThat){
    <span class='keyword'>return</span> aThis == <span class='keyword'>null</span> ? aThat == <span class='keyword'>null</span> : aThis.equals(aThat);
  }
  
  <span class='keyword'>private</span> <span class='keyword'>int</span> addHash(<span class='keyword'>int</span> aHash, Object aField){
    <span class='keyword'>int</span> result = <span class='literal'>37</span>*aHash;
    <span class='keyword'>if</span> (aField != <span class='keyword'>null</span>){
      result = result + aField.hashCode();
    }
    <span class='keyword'>return</span> result;
  }
  
  <span class='comment'>/** Utility method.  */</span>
  <span class='keyword'>private</span> <span class='keyword'>static</span> &lt;T <span class='keyword'>extends</span> Comparable&lt;T&gt;&gt; <span class='keyword'>int</span> comparePossiblyNull(T aThis, T aThat){
    <span class='keyword'>int</span> result = EQUAL;
    <span class='keyword'>int</span> BEFORE = -<span class='literal'>1</span>;
    <span class='keyword'>int</span> AFTER = <span class='literal'>1</span>;
    
    <span class='keyword'>if</span>(aThis != <span class='keyword'>null</span> &amp;&amp; aThat != <span class='keyword'>null</span>){ 
      result = aThis.compareTo(aThat);
    }
    <span class='keyword'>else</span> {
      <span class='comment'>//at least one reference is null - special handling
</span>      <span class='keyword'>if</span>(aThis == <span class='keyword'>null</span> &amp;&amp; aThat == <span class='keyword'>null</span>) {
        <span class='comment'>//do nothing - they are not distinct 
</span>      }
      <span class='keyword'>else</span> <span class='keyword'>if</span>(aThis == <span class='keyword'>null</span> &amp;&amp; aThat != <span class='keyword'>null</span>) {
        result = BEFORE;
      }
      <span class='keyword'>else</span> <span class='keyword'>if</span>( aThis != <span class='keyword'>null</span> &amp;&amp; aThat == <span class='keyword'>null</span>) {
        result = AFTER;
      }
    }
    <span class='keyword'>return</span> result;
  }
} 
</PRE>
<br>

Here is the custom TableModel, which translates a column index into a Comparator in its <tt>sortByColumn</tt> method.
<br>
<PRE>

<span class='keyword'>package</span> hirondelle.movies.edit;

<span class='keyword'>import</span> java.util.Collections;
<span class='keyword'>import</span> java.util.Comparator;
<span class='keyword'>import</span> java.util.List;
<span class='keyword'>import</span> hirondelle.movies.util.Util;
<span class='keyword'>import</span> javax.swing.table.AbstractTableModel;

<span class='comment'>/** 
  Table model used by {@link javax.swing.JTable}, explicitly for {@link Movie} objects.  
 
  &lt;P&gt;When a database operation occurs, the view is refreshed by calling 
  {@link #refreshView()}.
  &lt;P&gt;Note this class includes some methods which are unrelated to the needs of the 
  superclass, but are useful in the context of this application. 
 */</span>
<span class='keyword'>public</span> <span class='keyword'>final</span> <span class='keyword'>class</span> MovieTableModel <span class='keyword'>extends</span> AbstractTableModel {
  
  <span class='comment'>/** Constructor. */</span>
  <span class='keyword'>public</span> MovieTableModel(){
    fDAO = <span class='keyword'>new</span> MovieDAO();
    fMovies = fDAO.list();
  }

  <span class='comment'>/** 
   Explicitly refresh the view.
  
   &lt;P&gt;This style seems to be cleaner and simpler than implementing 
   a listener on the DAO.
  */</span> 
  <span class='keyword'>public</span> <span class='keyword'>void</span> refreshView() {
    fMovies = fDAO.list();
    <span class='comment'>//one might want to preserve the sort order here
</span>    fireTableDataChanged();
  }
  
  <span class='comment'>/** Returned the selected movie's id. */</span>
  <span class='keyword'>public</span> String getId(<span class='keyword'>int</span> aRow){
    Movie movie = fMovies.get(aRow);
    <span class='keyword'>return</span> movie.getId(); 
  }
  
  <span class='comment'>/** Return the selected {@link Movie}. */</span>
  <span class='keyword'>public</span> Movie getMovie(<span class='keyword'>int</span> aRow){
    <span class='keyword'>return</span> fMovies.get(aRow);
  }
  
  <span class='comment'>/** 
   Sort the movies.
   
   When called repeatedly, this method will toggle the sort between 
   ascending and descending.
   @param aIdx index of the column by which to sort.  
  */</span>
  <span class='keyword'>public</span> <span class='keyword'>void</span> sortByColumn(<span class='keyword'>int</span> aIdx){
    fNumClicks++;
    <span class='keyword'>if</span>( aIdx == <span class='literal'>1</span> ) {
      <span class='comment'>//natural sorting of the Movie class
</span>      Collections.sort(fMovies);
    }
    <span class='keyword'>else</span> {
      Comparator&lt;Movie&gt; comparator = <span class='keyword'>null</span>;
      <span class='keyword'>if</span> ( aIdx == <span class='literal'>0</span> ){
        comparator = Movie.TITLE_SORT;
      }
      <span class='keyword'>else</span> <span class='keyword'>if</span> ( aIdx == <span class='literal'>2</span> ){
        comparator = Movie.RATING_SORT;
      }
      <span class='keyword'>else</span> <span class='keyword'>if</span> ( aIdx == <span class='literal'>3</span> ){
        comparator = Movie.COMMENT_SORT;
      }
      Collections.sort(fMovies, comparator);
    }
    <span class='keyword'>if</span>( (fNumClicks % <span class='literal'>2</span>) == <span class='literal'>0</span>){
      Collections.reverse(fMovies);
    }
    fireTableDataChanged();
  }
  
  <span class='comment'>/** Return the number of columns in the table. */</span>
  <span class='keyword'>public</span> <span class='keyword'>int</span> getColumnCount() {
    <span class='keyword'>return</span> <span class='literal'>4</span>;
  }
  
  <span class='comment'>/** Return the number of rows in the table. */</span>
  <span class='keyword'>public</span> <span class='keyword'>int</span> getRowCount() {
    <span class='keyword'>return</span> fMovies.size();
  }
  
  <span class='comment'>/** Return the &lt;tt&gt;Object&lt;/tt&gt; in a specific table cell. */</span>
  <span class='keyword'>public</span> Object getValueAt(<span class='keyword'>int</span> aRow, <span class='keyword'>int</span> aCol) {
    Object result = <span class='keyword'>null</span>;
    Movie movie = fMovies.get(aRow);
    <span class='keyword'>if</span>(aCol == <span class='literal'>0</span>) {
      result = movie.getTitle();
    }
    <span class='keyword'>else</span> <span class='keyword'>if</span>(aCol == <span class='literal'>1</span>) {
      result = Util.format(movie.getDateViewed());
    }
    <span class='keyword'>else</span> <span class='keyword'>if</span>(aCol == <span class='literal'>2</span>) {
      result = movie.getRating();
    }
    <span class='keyword'>else</span> <span class='keyword'>if</span>(aCol == <span class='literal'>3</span>) {
      result = movie.getComment();
    }
    <span class='keyword'>return</span> result;
  }
  
  <span class='comment'>/** Return the name of a specific column. */</span>
  <span class='keyword'>public</span> String getColumnName(<span class='keyword'>int</span> aIdx){
    String result = <span class='literal'>""</span>;
    <span class='keyword'>if</span>( aIdx == <span class='literal'>0</span>) {
      result = <span class='literal'>"Title"</span>;
    }
    <span class='keyword'>else</span> <span class='keyword'>if</span>( aIdx == <span class='literal'>1</span>) {
      result = <span class='literal'>"Viewed"</span>;
    }
    <span class='keyword'>else</span> <span class='keyword'>if</span>( aIdx == <span class='literal'>2</span>) {
      result = <span class='literal'>"Rating"</span>;
    }
    <span class='keyword'>else</span> <span class='keyword'>if</span>( aIdx == <span class='literal'>3</span>) {
      result =  <span class='literal'>"Comment"</span>;
    }
    <span class='keyword'>return</span> result;
  }
  
  <span class='comment'>// PRIVATE //
</span>  <span class='keyword'>private</span> MovieDAO fDAO;
  <span class='keyword'>private</span> List&lt;Movie&gt; fMovies;
  <span class='keyword'>private</span> <span class='keyword'>int</span> fNumClicks = <span class='literal'>0</span>;
}
 
</PRE>
<br>

Finally, here is the GUI class which listens for column clicks, and passes them on to the custom TableModel.
Although the class is lengthy, the only items related to sorting are 
the <tt>SortMovieTable</tt> nested inner class
and the <tt>clickOnHeaderSortsTable</tt> method, near the end.
<br>
<PRE>

<span class='keyword'>package</span> hirondelle.movies.main;

<span class='keyword'>import</span> hirondelle.movies.LaunchApplication;
<span class='keyword'>import</span> hirondelle.movies.about.AboutAction;
<span class='keyword'>import</span> hirondelle.movies.edit.MovieActionAdd;
<span class='keyword'>import</span> hirondelle.movies.edit.MovieActionChange;
<span class='keyword'>import</span> hirondelle.movies.edit.MovieActionDelete;
<span class='keyword'>import</span> hirondelle.movies.edit.MovieTableModel;
<span class='keyword'>import</span> hirondelle.movies.exit.ExitAction;
<span class='keyword'>import</span> hirondelle.movies.util.Util;
<span class='keyword'>import</span> hirondelle.movies.util.ui.UiUtil;

<span class='keyword'>import</span> java.awt.Dimension;
<span class='keyword'>import</span> java.awt.Toolkit;
<span class='keyword'>import</span> java.awt.event.ActionEvent;
<span class='keyword'>import</span> java.awt.event.MouseAdapter;
<span class='keyword'>import</span> java.awt.event.MouseEvent;
<span class='keyword'>import</span> java.awt.Color;
<span class='keyword'>import</span> java.util.logging.Logger;

<span class='keyword'>import</span> javax.swing.*;
<span class='keyword'>import</span> javax.swing.event.ListSelectionEvent;
<span class='keyword'>import</span> javax.swing.event.ListSelectionListener;
<span class='keyword'>import</span> java.util.Locale;

<span class='comment'>/**
 Main window for the application.
 
 &lt;P&gt;A menu bar, and a sortable table containing the user's list of movies.
 
 &lt;P&gt;Some applications would add a confirmation dialog when the user exits.
*/</span>
<span class='keyword'>public</span> <span class='keyword'>final</span> <span class='keyword'>class</span> MainWindow {
  
  <span class='comment'>/** 
   Return an instance of this class.
   
   &lt;P&gt;This class is made a singleton, since there is only one main window. 
   Any caller can refresh the main window using
   &lt;PRE&gt;MainWindow.getInstance().refreshView();&lt;/PRE&gt;
   This lets the app avoid needing to pass around an object reference 
   to the main window.
  */</span>
  <span class='keyword'>public</span> <span class='keyword'>static</span> MainWindow getInstance() {
    <span class='keyword'>return</span> INSTANCE;
  }
  
  <span class='comment'>/**
   Build and display the main window.
   @param aUserName user name, as validated by 
   {@link hirondelle.movies.login.LoginController}. 
  */</span>
  <span class='keyword'>public</span> <span class='keyword'>void</span> buildAndShow(String aUserName){
    fUserName = aUserName;
    fLogger.fine(<span class='literal'>"Building GUI for user : "</span> + aUserName);
    buildGui();
  }
  
  <span class='comment'>/** Refresh the display in response to changing database content. */</span>
  <span class='keyword'>public</span> <span class='keyword'>void</span> refreshView(){
    fMovieTableModel.refreshView();
  }
  
  <span class='comment'>/** 
   Return the user name passed to {@link #buildAndShow(String)}.
   
  &lt;P&gt;The user name can be accessed anywhere using :
  &lt;PRE&gt;MainWindow.getInstance().getUserName();&lt;/PRE&gt;
  */</span>
  <span class='keyword'>public</span> String getUserName(){
    <span class='keyword'>return</span> fUserName;
  }
  
  <span class='comment'>// PRIVATE //
</span>  
  <span class='comment'>/** The single instance of this class. */</span>
  <span class='keyword'>private</span> <span class='keyword'>static</span> MainWindow INSTANCE = <span class='keyword'>new</span> MainWindow();
  
  <span class='comment'>/** Empty constructor prevents the caller from creating an object. */</span>
  <span class='keyword'>private</span> MainWindow() {  }
  
  <span class='keyword'>private</span> MovieTableModel fMovieTableModel;
  <span class='keyword'>private</span> JTable fMovieTable;
  <span class='keyword'>private</span> Action fChangeMovieAction;
  <span class='keyword'>private</span> Action fDeleteMovieAction;
  <span class='keyword'>private</span> String fUserName;
  <span class='keyword'>private</span> <span class='keyword'>static</span> <span class='keyword'>final</span> Logger fLogger = Util.getLogger(MainWindow.<span class='keyword'>class</span>);
  
  <span class='comment'>/** Build the user interface. */</span>
  <span class='keyword'>private</span> <span class='keyword'>void</span> buildGui(){
    JFrame frame = <span class='keyword'>new</span> JFrame(
      LaunchApplication.APP_NAME + 
      <span class='literal'>" - "</span> + fUserName.toUpperCase(Locale.ENGLISH)
    ); 
    
    fMovieTableModel = <span class='keyword'>new</span> MovieTableModel();
    fMovieTable = <span class='keyword'>new</span> JTable(fMovieTableModel);
    fMovieTable.setBackground(Color.LIGHT_GRAY);
    
    buildActionsAndMenu(frame);
    buildContent(frame);
    
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    placeInMiddlePartOfTheScreen(frame);
    addApplicationIcon(frame);
    UiUtil.centerAndShow(frame);
  }

  <span class='comment'>/** Sort the table.  Listens for clicks on the JTableHeader. */</span>
  <span class='keyword'>private</span> <span class='keyword'>final</span> <span class='keyword'>class</span> SortMovieTable <span class='keyword'>extends</span> MouseAdapter {
    <span class='keyword'>@Override</span> <span class='keyword'>public</span> <span class='keyword'>void</span> mouseClicked(MouseEvent aEvent) {
      fLogger.config(<span class='literal'>"Sorting the table."</span>);
      <span class='keyword'>int</span> columnIdx = fMovieTable.getColumnModel().getColumnIndexAtX(aEvent.getX());
      fMovieTableModel.sortByColumn(columnIdx);
    }
  }
  
  <span class='comment'>/** Show a dialog to edit a movie.  Listens for double-clicks on the JTable. */</span>
  <span class='keyword'>private</span> <span class='keyword'>final</span> <span class='keyword'>class</span> LaunchEditMovieDialog <span class='keyword'>extends</span> MouseAdapter {
    <span class='keyword'>@Override</span> <span class='keyword'>public</span> <span class='keyword'>void</span> mouseClicked(MouseEvent aEvent) {
      <span class='keyword'>if</span>( aEvent.getClickCount() == <span class='literal'>2</span>) {
        fLogger.config(<span class='literal'>"Editing a movie."</span>);
        ActionEvent event = <span class='keyword'>new</span> ActionEvent(<span class='keyword'>this</span>, <span class='literal'>0</span>, <span class='literal'>""</span>);
        fChangeMovieAction.actionPerformed(event);
      }
    }
  }
  
  <span class='comment'>/** Enable edit and delete actions only when something is selected in the table. */</span>
  <span class='keyword'>private</span> <span class='keyword'>final</span> <span class='keyword'>class</span> EnableEditActions <span class='keyword'>implements</span> ListSelectionListener {
    <span class='keyword'>public</span> <span class='keyword'>void</span> valueChanged(ListSelectionEvent aEvent) {
      fLogger.fine(
        <span class='literal'>"List selection changed. First:"</span> + aEvent.getFirstIndex() 
        + <span class='literal'>" Last "</span> + aEvent.getLastIndex()
      );
      <span class='keyword'>if</span>( aEvent.getFirstIndex() != -<span class='literal'>1</span>) {
        fDeleteMovieAction.setEnabled(<span class='keyword'>true</span>);
        fChangeMovieAction.setEnabled(<span class='keyword'>true</span>);
      }
      <span class='keyword'>else</span> {
        fDeleteMovieAction.setEnabled(<span class='keyword'>false</span>);
        fChangeMovieAction.setEnabled(<span class='keyword'>false</span>);
      }
    }
  }
  
  <span class='comment'>/** Build the menu bar. */</span>
  <span class='keyword'>private</span> <span class='keyword'>void</span> buildActionsAndMenu(JFrame aFrame) {
    JMenuBar menuBar = <span class='keyword'>new</span> JMenuBar();

    JMenu fileMenu = <span class='keyword'>new</span> JMenu(<span class='literal'>"File"</span>);
    fileMenu.setMnemonic(<span class='literal'>'F'</span>); 
    Action addMovieAction = <span class='keyword'>new</span> MovieActionAdd(aFrame);
    fileMenu.add(<span class='keyword'>new</span> JMenuItem(addMovieAction));
    fChangeMovieAction = <span class='keyword'>new</span> MovieActionChange(aFrame, fMovieTable, fMovieTableModel);
    fileMenu.add(<span class='keyword'>new</span> JMenuItem(fChangeMovieAction));
    fDeleteMovieAction = <span class='keyword'>new</span> MovieActionDelete(fMovieTable, fMovieTableModel);
    fileMenu.add(<span class='keyword'>new</span> JMenuItem(fDeleteMovieAction));
    
    Action exitAction = <span class='keyword'>new</span> ExitAction();
    fileMenu.add(<span class='keyword'>new</span> JMenuItem(exitAction));
    menuBar.add(fileMenu);
    
    JMenu helpMenu = <span class='keyword'>new</span> JMenu(<span class='literal'>"Help"</span>);
    helpMenu.setMnemonic(<span class='literal'>'H'</span>);
    helpMenu.add(<span class='keyword'>new</span> JMenuItem(<span class='keyword'>new</span> AboutAction(aFrame)));
    menuBar.add(helpMenu);
    
    aFrame.setJMenuBar(menuBar);
  }
  
  <span class='comment'>/** Expand the frame to fill the middel part of the screen. */</span>
  <span class='keyword'>private</span> <span class='keyword'>void</span> placeInMiddlePartOfTheScreen(JFrame aFrame) {
    Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    Dimension halfScreen = <span class='keyword'>new</span> Dimension(<span class='literal'>2</span>*screen.width/<span class='literal'>3</span>, screen.height/<span class='literal'>2</span>);
    aFrame.setPreferredSize(halfScreen);
  }

  <span class='comment'>/**
    Custom icon for the upper left corner of the frame.
    Not that a path relative to this class is used. 
   */</span>
  <span class='keyword'>private</span> <span class='keyword'>void</span> addApplicationIcon(JFrame aFrame) {
    ImageIcon icon = 
      UiUtil.createImageIcon(<span class='literal'>"app_icon.png"</span>, <span class='literal'>"Application icon"</span>, <span class='keyword'>this</span>.getClass())
    ;
    aFrame.setIconImage(icon.getImage());
  }

  <span class='comment'>/** Build the main content of the frame. */</span>
  <span class='keyword'>private</span> <span class='keyword'>void</span> buildContent(JFrame aFrame) {
    <span class='comment'>//relative column widths
</span>    fMovieTable.getColumnModel().getColumn(<span class='literal'>0</span>).setPreferredWidth(<span class='literal'>100</span>);
    fMovieTable.getColumnModel().getColumn(<span class='literal'>1</span>).setPreferredWidth(<span class='literal'>20</span>);
    fMovieTable.getColumnModel().getColumn(<span class='literal'>2</span>).setPreferredWidth(<span class='literal'>20</span>);
    fMovieTable.getColumnModel().getColumn(<span class='literal'>3</span>).setPreferredWidth(<span class='literal'>200</span>);
    
    <span class='comment'>/* 
     Interesting: even though these methods are one-liners, it's 
     still useful to create them, since, from the point of view of the caller, 
     they *greatly* clarify the intent, and 
     read at a higher level of abstraction. 
    */</span>
    clickOnHeaderSortsTable();
    doubleClickShowsEditDialog();
    rowSelectionEnablesActions();
    
    JScrollPane panel = <span class='keyword'>new</span> JScrollPane(fMovieTable);
    aFrame.getContentPane().add(panel);  
  }

  <span class='keyword'>private</span> <span class='keyword'>void</span> clickOnHeaderSortsTable() {
    fMovieTable.getTableHeader().addMouseListener(<span class='keyword'>new</span> SortMovieTable());
  }
  
  <span class='keyword'>private</span> <span class='keyword'>void</span> doubleClickShowsEditDialog() {
    fMovieTable.addMouseListener( <span class='keyword'>new</span> LaunchEditMovieDialog() );
  }

  <span class='keyword'>private</span> <span class='keyword'>void</span> rowSelectionEnablesActions() {
    fMovieTable.getSelectionModel().addListSelectionListener(<span class='keyword'>new</span> EnableEditActions());
  }
}
       
</PRE>
<br>
<br>
<br>

</div>




<div class='topic-section'>See Also :</div>
<div class='main-body'>
 
  
  <a href='TopicAction2161-2.html?Id=1'>Type-Safe Enumerations</a> <br>
 
  
  <a href='TopicAction789c-2.html?Id=161'>Indicate table sort</a> <br>
 
  
  <a href='TopicAction167e-2.html?Id=163'>Filter table rows</a> <br>
 
</div>


<div class='topic-section'>Would you use this technique?</div>
<div class='main-body'>
  
  <form action="http://www.javapractices.com/vote/AddVoteAction.do" method='post'>
    Yes<input type='radio' name='Choice' value='Y' >
    &nbsp;&nbsp;No<input type='radio' name='Choice' value='N'>
    &nbsp;&nbsp;Undecided<input type='radio' name='Choice' value="?" >
    &nbsp;&nbsp;<input type=submit value="Vote" >
    <input type='hidden' name='Operation' value='Apply'>
    <input type='hidden' name='TopicId' value='162'>
  </form>
</div>

<div style='height:10.0em;'></div>

 
 
</div>

  

 





<div align='center' class='legalese'>  
&copy; 2011 Hirondelle Systems |
<a href='../source/SourceAction-2.html'><b>Source Code</b></a><IMG class='no-margin' SRC="../images/goldstar.gif" ALT=""> |
<a href="mailto:webmaster@javapractices.com">Contact</a> |
<a href="http://creativecommons.org/licenses/by-nc-sa/1.0/">License</a> |
<a href='../apps/cjp.rss'>RSS</a>
<!-- ukey="2AC36CD2" -->
<!-- ckey="16DF3D87" -->
<br>

 Individual code snippets can be used under this <a href='../LICENSE.txt'>BSD license</a> - Last updated on June 6, 2010.<br>
 Over 150,000 unique IPs last month - <span title='Java Practices 2.6.5, Mon May 16 00:00:00 EDT 2011'>Built with</span> <a href='http://www.web4j.com/'>WEB4J</a>.<br>
 - In Memoriam : Bill Dirani -
</div>

<script src="../../www.google-analytics.com/urchin.js" type="text/javascript">
</script>
<script type="text/javascript">
_uacct = "UA-2633428-1";
urchinTracker();
</script>



</body>

<!-- Mirrored from www.javapractices.com/topic/TopicAction.do?Id=162 by HTTrack Website Copier/3.x [XR&CO'2010], Sun, 12 Jun 2011 17:28:07 GMT -->
<!-- Added by HTTrack --><meta http-equiv="content-type" content="text/html;charset=UTF-8"><!-- /Added by HTTrack -->
</html>
